package com.dingwen.treasure.manage.service.impl;

import cn.hutool.core.util.StrUtil;
import com.dingwen.treasure.base.exception.AuthException;
import com.dingwen.treasure.common.redis.service.RedisService;
import com.dingwen.treasure.manage.config.LoginProperties;
import com.dingwen.treasure.manage.service.LoginService;
import com.dingwen.treasure.model.entity.mybatisplus.SysUser;
import com.dingwen.treasure.model.vo.LoginVo;
import com.dingwen.treasure.model.vo.SysMenuVo;
import com.dingwen.treasure.mybatisplus.service.SysMenuService;
import com.dingwen.treasure.security.dto.LoginUser;
import com.dingwen.treasure.security.service.TokenService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * 　LoginServiceImpl
 * 　@author dingwen
 * 　@date 2022/6/12
 */
@Service
@Slf4j
public class LoginServiceImpl implements LoginService {

    /**
     * 认证管理器
     */
    @Resource
    private AuthenticationManager authenticationManager;

    /**
     * redis 服务
     */
    @Resource
    private RedisService redisService;


    /**
     * sysMenuService
     */
    @Resource
    private SysMenuService sysMenuService;

    /**
     * 登录配置
     */
    @Resource
    private LoginProperties loginProperties;


    /**
     * token service
     */
    @Resource
    private TokenService tokenService;


    /**
     * 登录
     *
     * @param username 用户名
     * @param password 密码
     * @return {@link LoginUser}
     */
    @Override
    public LoginUser doLogin(String username, String password) {
        // 2.登录次数重试限制 redis 实现
        lock(username);

        //security 会自动调用 UserDetailsServiceImpl loadUserByUsername 方法
        try {
            UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
                    new UsernamePasswordAuthenticationToken(username, password);
            // 认证信息
            Authentication authentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
            LoginUser loginUser = (LoginUser) authentication.getPrincipal();
            // 缓存到redis
            // 登录信息缓存到redis
            tokenService.setLoginUser(loginUser, loginUser.getUsername());

            return loginUser;
        } catch (AuthenticationException e) {
            //登录次数限制
            limit(username);
            customExceptionInfo(e);
        }
        throw new AuthException(HttpStatus.UNAUTHORIZED.value(), "登录失败");

    }


    /**
     * 生成登录vo
     *
     * @param sysUser 用户
     * @param token   令牌
     * @return {@link LoginVo}
     */
    @Override
    public LoginVo buildLoginVo(SysUser sysUser, String token) {
        // 账号
        String account = sysUser.getAccount();

        //  优先返回缓存信息
        LoginVo loginVo = tokenService.getLoginVO(account);
        if (Objects.nonNull(loginVo)) {
            return loginVo;
        }

        List<SysMenuVo> menuVos = sysMenuService.getSysMenusForTree(sysUser.getId());
        loginVo = LoginVo.builder()
                .sysMenuVos(menuVos)
                .token(token)
                .sysUser(sysUser)
                .build();
        // 缓存用户视图信息
        tokenService.setLoginVO(loginVo, account);
        return loginVo;
    }


    /**
     * 判断用户是否被锁定
     *
     * @param account 账户
     * @return boolean
     */
    @Override
    public void lock(String account) {
        String key = getKey(account);
        Boolean has = redisService.hasKey(key);
        if (has && loginProperties.getMaxRetries() == redisService.getCacheObject(key)) {
            //key 存在 并且 已锁定(输入次数达到限制)
            String message = StrUtil.format("登录次数过多，账号已锁定，请 {} 分钟后重试", loginProperties.getLockTime());
            throw new AuthException(HttpStatus.LOCKED.value(), message);
        }
    }

    /**
     * 登录限制
     *
     * @param account 账户
     */
    @Override
    public void limit(String account) {
        String key = getKey(account);
        // 次数
        Long count = redisService.increment(key);
        redisService.setExpire(key, loginProperties.getLockTime(), TimeUnit.MINUTES);
        if (count >= loginProperties.getMaxRetries()) {
            redisService.setCacheObject(key, loginProperties.getMaxRetries(), loginProperties.getLockTime(),
                    TimeUnit.MINUTES);
        }
    }

}
